package com.qire.common.basic;

import com.qire.common.function.Function;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.concurrent.TimeUnit;

public enum DateUtil {
    /** 格式：HH:mm */
    Hm(new SimpleDateFormat("HH:mm")),
    /** 格式：yyyy-MM-dd */
    yMd(new SimpleDateFormat("yyyy-MM-dd")),
    /** 格式：yyyy-MM-dd HH:mm:ss */
    yMdHms(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

    public final SimpleDateFormat dateFormat;

    DateUtil(SimpleDateFormat dateFormat) {
        this.dateFormat = dateFormat;
    }

    /**
     * 获取当前时间并格式化
     * @return 当前时间对于格式的字符串。
     */
    public String currentTime() {
        return dateFormat.format(new Date());
    }

    /**
     * 判断当前时间介于某个时间段之间
     * @param beginTime 起始时间
     * @param endTime 结束时间
     * @return true 在之间 false 不在
     */
    public boolean nowTimeAtBetween(String beginTime, String endTime) {
        Calendar now = Calendar.getInstance(); now.setTime(parse(currentTime()));
        Calendar begin = Calendar.getInstance(); begin.setTime(parse(beginTime));
        Calendar end = Calendar.getInstance(); end.setTime(parse(endTime));

        // 修正时间跨天的情况
        if (end.getTimeInMillis() < begin.getTimeInMillis()) {
            end.add(Calendar.DATE, 1);
        }
        if (now.getTimeInMillis() < begin.getTimeInMillis() && now.getTimeInMillis() < end.getTimeInMillis()) {
            now.add(Calendar.DATE, 1);
        }
        return now.getTimeInMillis() >= begin.getTimeInMillis() && now.getTimeInMillis() <= end.getTimeInMillis();
    }

    /**
     * 获取当前时间的一个偏移时间并格式化
     * @param offset 偏移值
     * @param calendarUnit 偏移值单位，如： {@link Calendar#DATE}
     * @return
     */
    public String offsetTime(int offset, int calendarUnit) {
        Calendar calendar1 = Calendar.getInstance();
        calendar1.add(calendarUnit, offset);
        return dateFormat.format(calendar1.getTime());
    }

    /**
     * 解析字符串为Data类型
     * @param dataString 时间字符串
     * @return Date 时间
     */
    public Date parse(String dataString) {
        try {
            return dateFormat.parse(dataString);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 格式化 date 为对应格式
     * @param date 时间
     * @return String 时间
     */
    public String format(Date date) {
        return dateFormat.format(date);
    }

    /**
     * 将一个符合当前格式的时间字符串转换成目标格式的时间字符串
     * @param dateString 时间字符串
     * @param dateUtil 目标格式
     * @return 期望的时间字符串
     */
    public String to(String dateString, DateUtil dateUtil) {
        Date date = parse(dateString);
        if(date == null) {
            return "Invalid Date";
        }
        String dateStr = dateUtil.format(date);
        return dateStr;
    }




    /**
     * 检查时间字串是否合法有效，能被当前格式解析，或是一个有效日期。不能是0000-00-00之类。
     * @param fromTime 日期时间字串
     * @return true 有效  false 无效
     */
    public boolean valid(String fromTime) {
        if("0000-00-00".equals(fromTime) || "0000-00-00 00:00:00".equals(fromTime) || StringUtil.isEmpty(fromTime)) {
            return false;
        }
        try {
            dateFormat.parse(fromTime).getTime();
            return true;
        } catch (ParseException e) {
            return false;
        }
    }

    /**
     * 计算起止时间差，公式：{@code toTime - fromTime}
     * @param fromTime long 开始时间
     * @param toTime   long 终止时间
     * @return 时间包装
     */
    public static TimeWrapper calcTimeDiff(long fromTime, long toTime) {
        return calcTimeDiff(fromTime, toTime, true);
    }
    public static TimeWrapper calcTimeDiff(long fromTime, long toTime, boolean isCorrection) {
        if(isCorrection) {
            long unitDayToMs = (1000 * 60 * 60 * 24);
            // 修正不满1天的时间却在下一天的情况
            fromTime = (fromTime - (fromTime % unitDayToMs)) + 1;
            // 修正不满1天的时间却在下一天的情况
            toTime  = (toTime  - (toTime  % unitDayToMs)) + 1;
        }
        return new TimeWrapper(toTime - fromTime);
    }

    /**
     * 计算从某时起到某时的差值
     * @param sometimeFrom
     * @param sometimeTo
     * @return
     */
    public TimeWrapper fromTo(String sometimeFrom, String sometimeTo) {
        return fromTo(sometimeFrom, sometimeTo, true);
    }
    public TimeWrapper fromTo(String sometimeFrom, String sometimeTo, boolean isCorrection) {
        Date sometimeFromData = parse(sometimeFrom);
        Date sometimeToDate = parse(sometimeTo);
        return calcTimeDiff(sometimeFromData.getTime(), sometimeToDate.getTime(), isCorrection);
    }

    /**
     * 计算当前时间到某一时刻的剩余时间
     * @param somedayTime 结束时间
     * @return 时间包装
     */
    // todo: 改名 toSometime
    public TimeWrapper toSomeday(String somedayTime) {
        return toSomeday(somedayTime, true);
    }
    public TimeWrapper toSomeday(String somedayTime, boolean isCorrection) {
        Date somedayDate = parse(somedayTime);
        return somedayDate == null ? new TimeWrapper(0) : toSomeday(somedayDate, isCorrection);
    }
    public TimeWrapper toSomeday(Date somedayDate) {
        return toSomeday(somedayDate, true);
    }
    public TimeWrapper toSomeday(Date somedayDate, boolean isCorrection) {
        return toSomeday(somedayDate.getTime(), isCorrection);
    }
    public TimeWrapper toSomeday(long somedayTime) {
        return toSomeday(somedayTime, true);
    }
    public TimeWrapper toSomeday(long somedayTime, boolean isCorrection) {
        long nowTime = new Date().getTime();
        return calcTimeDiff(nowTime, somedayTime, isCorrection);
    }

    /**
     * 计算某一时刻到当前时间的剩余时间
     * @param somedayTime
     * @return
     */
    // todo 改名 toNow
    public TimeWrapper toToday(String somedayTime) {
        return toToday(somedayTime, true);
    }
    public TimeWrapper toToday(String somedayTime, boolean isCorrection) {
        Date somedayDate = parse(somedayTime);
        return somedayDate == null ? new TimeWrapper(0) : toToday(somedayDate, isCorrection);
    }
    public TimeWrapper toToday(Date somedayDate) {
        return toToday(somedayDate, true);
    }
    public TimeWrapper toToday(Date somedayDate, boolean isCorrection) {
        return toToday(somedayDate.getTime(), isCorrection);
    }
    public TimeWrapper toToday(long somedayTime) {
        return toToday(somedayTime, true);
    }
    public TimeWrapper toToday(long somedayTime, boolean isCorrection) {
        long nowTime = new Date().getTime();
        return calcTimeDiff(somedayTime, nowTime, isCorrection);
    }



    /**
     * 断言到今天的时间还在 限制时间（limit） 内有效
     * @param fromTime 指定时间
     * @param limit 限制时间或过期时间
     * @return 如果在限制时间内，则有效返回 true，否则 false
     */
    public boolean assertToTodayValid(long fromTime, int limit) {
        return assertToTodayValid(fromTime, limit, true);
    }

    /**
     * 断言到今天的时间还在 限制时间（limit） 内有效
     * @param fromTime 指定时间
     * @param limit 限制时间或过期时间
     * @param isUnitDay 限制时间的单位，true 为天，false 为毫秒
     * @return 如果在限制时间内，则有效返回 true，否则 false
     */
    public boolean assertToTodayValid(long fromTime, int limit, boolean isUnitDay) {
        if(isUnitDay) {
            return toToday(fromTime).formatBy(TimeUnit.MILLISECONDS::toDays) < limit;
        } else {
            return calcTimeDiff(fromTime, new Date().getTime(), false).time < limit;
        }
    }

    /**
     * 断言到今天的时间还在 限制时间（limit） 内有效
     * @param fromTime 指定时间
     * @param limit 限制时间或过期时间
     * @return 如果在限制时间内，则有效返回 true，否则 false
     */
    public boolean assertToTodayValid(String fromTime, int limit) {
        return assertToTodayValid(fromTime, limit, true);
    }

    /**
     * 断言到今天的时间还在 限制时间（limit） 内有效
     * @param fromTime 指定时间
     * @param limit  限制时间或过期时间
     * @param isUnitDay 限制时间的单位，true 为天，false 为毫秒
     * @return 如果在限制时间内，则有效返回 true，否则 false
     */
    public boolean assertToTodayValid(String fromTime, int limit, boolean isUnitDay) {
        if(isUnitDay) {
            return toToday(fromTime).formatBy(TimeUnit.MILLISECONDS::toDays) < limit;
        } else {
            Date fromDate = parse(fromTime);
            if(fromDate == null) {
                return false;
            }
            return calcTimeDiff(fromDate.getTime(), new Date().getTime(), false).time < limit;
        }
    }

    private Function<Object, ?> transaction;

    private Object transaction(Object date) {
        if(date instanceof String) {
            return parse((String) date);
        }
        if(date instanceof Date) {
            return format((Date) date);
        }
        return date;
    }

    public DateUtil transaction(final DateUtil dateUtil) {
        Function<Object, ?> function = date -> dateUtil.transaction(date);
        transaction = transaction == null ? function : transaction.andThen(function);
        return this;
    }

    public <T,P> T runTransform(P date) {
        try {
            if(transaction != null) {
                return (T) transaction(transaction.apply(date));
            } else {
                return (T) transaction(date);
            }
        } finally {
            transaction = null;
        }
    }

    /**
     * 时差包装类，用于计算时间差格式化
     */
    public static final class TimeWrapper {
        /** 时间数据 */
        public final long time;
        private final TimeUnit timeUnit = TimeUnit.MILLISECONDS;

        public TimeWrapper(long time) {
            this.time = time;
        }

        public TimeWrapper correction() {
            return this;
        }

        /**
         * 断言是否在 limit 限制内
         * @param limit 有效时间
         * @param isUnitDay 单位： true 天  false 毫秒
         * @return true 有效， false 无效
         */
        public boolean assertValid(int limit, boolean isUnitDay) {
            long timeNumber = isUnitDay ? formatBy(timeUnit::toDays) : time;
            return timeNumber < limit;
        }

        public String autoFormat() {
            return autoFormat(-1);
        }
        public String autoFormat(int showLimit) {
            return autoFormat("", "", showLimit);
        }
        public String autoFormat(String prefix, String suffix, int showLimit) {
            int[] showCount = {0};

            StringBuilder timeBuilder = new StringBuilder();
            timeBuilder.append(prefix);
            long days = timeUnit.toDays(time);
            if(days > 0 && allow(showCount, showLimit)) {
                timeBuilder.append(days).append("天");
            }
            long hours = timeUnit.toHours(time) % 24;
            if(hours > 0 && allow(showCount, showLimit)) {
                timeBuilder.append(hours).append("时");
            }
            long minutes = timeUnit.toMinutes(time) % 60;
            if(minutes > 0 && allow(showCount, showLimit)) {
                timeBuilder.append(minutes).append("分");
            }
            long seconds = timeUnit.toSeconds(time) % 60;
            if(seconds > 0 && allow(showCount, showLimit)) {
                timeBuilder.append(seconds).append("秒");
            }
            timeBuilder.append(suffix);
            return timeBuilder.toString();
        }

        private boolean allow(int[] showCount, int showLimit) {
            boolean allow = showLimit <= 0 || showCount[0] < showLimit;
            if(allow || showCount[0] >= 1) {
                showCount[0]++;
            }
            return allow;
        }

        public String formatBy(Formatter formatter, String prefix, String suffix) {
            return prefix + formatBy(formatter) + suffix;
        }

        public long formatBy(Formatter formatter) {
            return formatter.format(time);
        }
        // todo: 需要定义一个静态默认的转换方法
        public interface Formatter {
            long format(long time);
        }
    }

}
